﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace GenericLibrary.Common {

    public class DoubleKahanSummation {

        private double _positiveSum = 0, _positiveCorrection = 0, _negativeSum = 0, _negativeCorrection = 0;

        public void Add( double addend ) {
            try {
                double correctedAddend, tempSum;
                if( addend > 0 ) {
                    correctedAddend = addend + this._positiveCorrection;
                    tempSum = this._positiveSum + correctedAddend;
                    this._positiveCorrection = correctedAddend - ( tempSum - this._positiveSum );
                    this._positiveSum = tempSum;
                } else if( addend < 0 ) {
                    correctedAddend = addend + this._negativeCorrection;
                    tempSum = this._negativeSum + correctedAddend;
                    this._negativeCorrection = correctedAddend - ( tempSum - this._negativeSum );
                    this._negativeSum = tempSum;
                }
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        public double Value {
            get {
                try {
                    return ( this._positiveSum + this._negativeSum ) + ( this._positiveCorrection + this._negativeCorrection );
                } catch( Exception ) {
                    Debug.Assert( false );
                    throw;
                }
            }
        }

        public static DoubleKahanSummation operator +( DoubleKahanSummation runningSum, double addend ) {
            try {
                runningSum.Add( addend );
                return runningSum;
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        public static DoubleKahanSummation operator +( double addend, DoubleKahanSummation runningSum ) {
            try {
                runningSum.Add( addend );
                return runningSum;
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }
    }

    public interface IArithmetic<T> : IComparer<T> {

        T Default { get; }
        T Add( T x, T y );
        T Substract( T x, T y );
        T Multiply( T x, T y );
        T Divide( T x, T y );
    }

    public class KahanSummation<T> {

        private IArithmetic<T> _arithmetic;
        private T _default, _positiveSum, _positiveCorrection, _negativeSum, _negativeCorrection;

        public KahanSummation( IArithmetic<T> arithmetic ) {
            this._arithmetic = arithmetic;
            this._default = this._arithmetic.Default;
            this._positiveSum = this._arithmetic.Default;
            this._positiveCorrection = this._arithmetic.Default;
            this._negativeSum = this._arithmetic.Default;
            this._negativeCorrection = this._arithmetic.Default;
        }

        public void Add( T addend ) {
            try {
                int order = this._arithmetic.Compare( addend, this._default );
                T correctedAddend, tempSum;
                if( order > 0 ) {
                    correctedAddend = this._arithmetic.Add( addend, this._positiveCorrection );
                    tempSum = this._arithmetic.Add( this._positiveSum, correctedAddend );
                    this._positiveCorrection = this._arithmetic.Substract( correctedAddend, this._arithmetic.Substract( tempSum, this._positiveSum ) );
                    this._positiveSum = tempSum;
                } else if( order < 0 ) {
                    correctedAddend = this._arithmetic.Add( addend, this._negativeCorrection );
                    tempSum = this._arithmetic.Add( this._negativeSum, correctedAddend );
                    this._negativeCorrection = this._arithmetic.Substract( correctedAddend, this._arithmetic.Substract( tempSum, this._negativeSum ) );
                    this._negativeSum = tempSum;
                }
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        public void Add( KahanSummation<T> other ) {
            try {
                if( other != null ) {
                    this.Add( other._positiveSum );
                    this.Add( other._positiveCorrection );
                    this.Add( other._negativeSum );
                    this.Add( other._negativeCorrection );
                }
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        public T Value {
            get {
                try {
                    return this._arithmetic.Add( this._arithmetic.Add( this._positiveSum, this._negativeSum ), this._arithmetic.Add( this._positiveCorrection, this._negativeCorrection ) );
                } catch( Exception ) {
                    Debug.Assert( false );
                    throw;
                }
            }
        }

        public static KahanSummation<T> operator +( KahanSummation<T> sum, T addend ) {
            try {
                sum.Add( addend );
                return sum;
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        public static KahanSummation<T> operator +( T addend, KahanSummation<T> sum ) {
            try {
                sum.Add( addend );
                return sum;
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }
    }

    public class DoubleArithmetic : IArithmetic<double> {

        #region IArithmetic<double> Members

        public double Default {
            get { return default( double ); }
        }

        public double Add( double x, double y ) {
            return x + y;
        }

        public double Substract( double x, double y ) {
            return x - y;
        }

        public double Multiply( double x, double y ) {
            return x * y;
        }

        public double Divide( double x, double y ) {
            return x / y;
        }

        #endregion

        #region IComparer<double> Members

        public int Compare( double x, double y ) {
            return x.CompareTo( y );
        }

        #endregion
    }

    public class SingleArithmetic : IArithmetic<float> {


        #region IArithmetic<float> Members

        public float Default {
            get { return default( float ); }
        }

        public float Add( float x, float y ) {
            return x + y;
        }

        public float Substract( float x, float y ) {
            return x - y;
        }

        public float Multiply( float x, float y ) {
            return x * y;
        }

        public float Divide( float x, float y ) {
            return x / y;
        }

        #endregion

        #region IComparer<float> Members

        public int Compare( float x, float y ) {
            return x.CompareTo( y );
        }

        #endregion
    }
}
